/*
 * Event.java
 * 
 *  author: Daniel Rudigier
 *    date: 01.12.2008
 * 
 * 
 */

package at.syncme.framework;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

import de.enough.polish.io.Serializer;

/**
 * basic event class for network synchronization. this events listed here are
 * generic, and should be extended by the concrete game.
 * <p>
 * event ids are of type short.
 * </p>
 * <p>
 * each game follows a state diagram. that has these states:
 * <ul>
 * <li>preparation / setup</li>
 * </p>
 * <li>loading</li>
 * </p>
 * <li>running</li>
 * </p>
 * <li>finished</li>
 * </p>
 * </ul>
 * </p>
 * <p>
 * priorities are also available. depending on the synchronization used, they
 * will be taken into consideration or not. f.e. events have to be acknowledged
 * by as a requirement of the used synchronization algorithm
 * </p>
 * <p>
 * the serialization is also done in this class, but not using the java way by
 * implementing Serializable. this has too much overhead, since the transferred
 * data should be kept small. serialization breaks inheritance and therefore a
 * design approach having different event classes. with aspects, the static
 * structure could be changed. since this will happen quite often during the
 * game it's considerable if the code is advised by aspects in here
 * </p>
 * 
 * @author Daniel Rudigier
 */
public final class Event {

    /**
     * game preparing
     */
    public static final short GAME_PREPARE = id();

    /**
     * game loading
     */
    public static final short GAME_PREPARED = id();

    /**
     * game running
     */
    public static final short GAME_RUN = id();

    /**
     * game finished
     */
    public static final short GAME_FINISHED = id();

    /**
     * new player joined
     */
    public static final short PLAYER_CHALLANGE = id();
    
    /**
     * player connected
     */
    public static final short PLAYER_CONNECTED = id();

    /**
     * player left
     */
    public static final short PLAYER_DISCONNECTED = id();

    /**
     * game state
     */
    public static final short GAME_STATE = id();
    
    /*
     * setup events
     */
    
    /**
     * setup server event
     */
    public static final short SETUP_SERVER = id(); 
    
    /**
     * setup client event
     */
    public static final short SETUP_CLIENT = id();
    
    /**
     * connect event
     */
    public static final short SETUP_CONNECT = id();
    
    /**
     * disconnect event
     */
    public static final short SETUP_DISCONNECT = id();
    
    /**
     * exit internal setup 
     */
    public static final short SETUP_EXIT = id();
    
    /**
     * start game 
     */
    public static final short SETUP_START = id();

    /*
     * priorities
     */
    /**
     * high priority
     */
    public static final byte PRIORITY_HIGH = 10;

    /**
     * low priority
     */
    public static final byte PRIORITY_LOW = 1;

    /*
     * determine origin
     */
    /**
     * no origin node id. these events are created within the game itself and
     * haven't been transferred by an underlying network manager
     */
    private static final short NO_ORIGIN = -1;

    private static short NEXT_EVENT_ID;

    /*
     * event properties
     */

    private short id;

    private short origin;

    private Object message;

    private boolean complex;

    private byte priority;

    private long created;

    private long acknowledged;

    /**
     * private constructor
     * 
     * @param id
     * @param nodeId
     * @param complex
     * @param message
     * @param priority
     * @param created
     * @param acknowledged
     */
    private Event(short id, short nodeId, long created, long acknowledged,
            boolean complex, Object message, byte priority) {
        this.id = id;
        this.origin = nodeId;
        this.created = created;
        this.acknowledged = acknowledged;
        this.message = message;
        this.priority = priority;
    }

    /**
     * @return id
     */
    public short getId() {
        return id;
    }

    /**
     * generate an event id (public for other classes)
     * 
     * @return the next event id
     */
    public static short id() {
        return NEXT_EVENT_ID++;
    }

    /**
     * @return message
     */
    public Object getMessage() {
        return message;
    }

    /**
     * @return priority
     */
    public byte getPriority() {
        return priority;
    }

    /**
     * @return created
     */
    public long getCreated() {
        return created;
    }

    /**
     * @return acknowledged
     */
    public long getAcknowledged() {
        return acknowledged;
    }

    /**
     * the original node id (starting from 0)
     * 
     * @return origin
     */
    public short getOrigin() {
        return origin;
    }

    /**
     * acknowledge an event initiated by an client. set
     * 
     * @param e
     * @return
     */
    public Event acknowledge() {
        // let there be an advice
        return this;
    }

    /**
     * reject an event (server full, password protected, etc.)
     * 
     * @param e
     * @return
     */
    public Event reject() {
        this.acknowledged = 0;
        return this;
    }

    /**
     * return if this event is an acknowledge event
     * 
     * @return true if ack'd
     */
    public boolean isAcknowledged() {
        return this.acknowledged > 0;
    }

    /**
     * return if this event was created locally
     * 
     * @return
     */
    public boolean isLocal() {
        return this.origin == NO_ORIGIN;
    }

    /**
     * create a generic event without a message.
     * 
     * @param id
     * @param priority
     * @return
     */
    public static Event create(short id, byte priority) {
        return new Event(id, NO_ORIGIN, 0, 0, false, "empty".getBytes(),
                priority);
    }
    
    /**
     * create a generic event with a message payload.
     * 
     * @param id
     * @param payload
     * @param priority
     * @return
     */
    public static Event create(short id, Object payload, byte priority) {
        return new Event(id, NO_ORIGIN, 0, 0, true, payload, priority);
    }

    /**
     * create an generic event that originates from network (disconnect, etc.)
     * 
     * @param id
     * @param origin
     * @return
     */
    public static Event createFromNetwork(short id, short origin) {
        return new Event(id, origin, 0, 0, false, "net".getBytes(),
                PRIORITY_HIGH);
    }

    /**
     * serialize and output event
     * 
     * @param e
     * @param out
     * @throws IOException
     */
    public void serialize(DataOutputStream out) throws IOException {
        out.writeShort(this.id);
        out.writeBoolean(this.complex);

        if (!this.complex) {
            out.writeInt(((byte[]) this.message).length);
            out.write((byte[]) this.message);
        } else {
            Serializer.serialize(this.message, out);
        }

        out.writeByte(this.priority);
        out.writeLong(this.created);
        out.writeLong(this.acknowledged);
    }

    /**
     * materialize the event
     * 
     * @param in
     * @param nodeId
     * @return the materialized event
     * @throws IOException
     */
    public static Event materialize(DataInputStream in, short nodeId)
            throws IOException {
        short id = in.readShort();
        boolean complex = in.readBoolean();

        Object message;
        if (!complex) {
            int msgLength = in.readInt();
            byte[] buffer = new byte[msgLength];
            in.read(buffer, 0, msgLength);
            message = buffer;
        } else {
            message = Serializer.deserialize(in);
        }

        byte priority = in.readByte();
        long created = in.readLong();
        long acknowledged = in.readLong();

        return new Event(id, nodeId, created, acknowledged, complex, message, priority);
    }

    /**
     * sign the current creation time
     * 
     * @param created
     */
    public void setCreated(long created) {
        this.created = created;
    }

    /**
     * sign the acknowledge time
     * 
     * @param acknowledged
     *            to set
     */
    public void setAcknowledged(long acknowledged) {
        this.acknowledged = acknowledged;
    }

    /**
     * set the message
     * 
     * @param message
     *            to set
     */
    public void setSimple(byte[] message) {
        this.complex = false;
        this.message = message;
    }

    /**
     * set the message as complex data type to serialize
     * 
     * @param complex
     */
    public void setComplex(Object complex) {
        this.complex = true;
        this.message = complex;
    }


}
